﻿namespace Typps
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Data;
    using System.Drawing.Design;
    using System.IO;
    using System.Linq;
    using System.Resources;
    using System.Text;
    using System.Web;
    using System.Web.Hosting;
    using System.Web.UI;
    using System.Web.UI.WebControls;

    /*
        we can't use IDataBound interface since it's a new feature in .net 4 ( this will limit us to .net 4 runtime and we don't want this dependency).
        someday when .net 4 is in wide usage, we shall just implement the interface instead of inheriting DataControlControl. This will enable us to just
        inherit ControlBase and avoid repeating all methods in ControlBase here.
        For now we are manually synchronizing this class with that of ControlBase.
     */
    public abstract class DataBoundControlBase : DataBoundControl, INamingContainer, ICompositeControlDesignerAccessor, IScriptControl, ITheming, IControlBase
    {
        #region Fields

        string _callbackArgument = string.Empty;
        bool _formatCss = false;
        string[] _manifestResourceNamesCache;
        string _rootImageSkinFolder = "Typps.Resources.Skins.{0}.Images.Editor.{1}";
        string _rootSkinFolder = "Typps.Resources.Skins.{0}.{1}";
        string _rootTemplateSkinFolder = "Typps.Resources.Skins.{0}.Templates.{1}";

        #endregion Fields

        #region Constructors

        protected DataBoundControlBase()
        {
            _manifestResourceNamesCache = GetType().Assembly.GetManifestResourceNames();
            RM = Properties.Resources.ResourceManager;
        }

        #endregion Constructors

        #region Events

        internal event EventHandler SelectedSkinChanged;

        #endregion Events

        #region Properties

        /// <summary>
        /// Gets a ClientScriptManager object used to manage, register, and add script to the page
        /// </summary>
        [Browsable(false)]
        public virtual ClientScriptManager ClientScript
        {
            get
            {
                return (Page != null) ? Page.ClientScript : null;
            }
        }

        [Browsable(false)]
        public virtual Type ComponentType
        {
            get
            {
                return this.GetType();
            }
        }

        // Properties
        public override ControlCollection Controls
        {
            get
            {
                this.EnsureChildControls();
                return base.Controls;
            }
        }

        /// <summary>
        /// The order in which this property is set matters greatly. You must
        /// first set CustomSkinFolder and then try and set this property, since
        /// it is a dependant property. 
        /// </summary>
        [TypeConverter(typeof(SkinConverter)),
        DefaultValue("None"),
        RefreshProperties(RefreshProperties.Repaint)]
        public virtual string CustomSkin
        {
            get { return (string)(ViewState["CustomSkin"] ?? "None"); }
            set
            {
                ViewState["CustomSkin"] = value;
                if (DesignMode)
                {
                    ResetCollectionsAndStyles();
                    OnSelectedSkinChanged(EventArgs.Empty);
                }
            }
        }

        /// <summary>
        /// The folder containing your custom skin. Please note that you 
        /// have to follow a pattern when creating custom skins
        /// </summary>
        [Editor(typeof(FolderNameBrowser), typeof(UITypeEditor)),
        DefaultValue("")]
        public virtual string CustomSkinFolder
        {
            get
            {
                return (string)(ViewState["CustomSkinFolder"] ?? string.Empty);
            }
            set
            {
                ViewState["CustomSkinFolder"] = value;
                if (DesignMode)
                {
                    CustomSkin = "None";
                    ResetCollectionsAndStyles();
                }
            }
        }

        [Browsable(false)]
        public new bool DesignMode
        {
            get
            {
                return (HttpContext.Current == null);
            }
        }

        /// <summary>
        /// If you supply an external css file, this control will use that instead
        /// of referencing the css embedded in this control as a resource.
        /// Before including an external stylesheet you may use the "Get autogenerated css" 
        /// option exposed by this control in design mode within the vs.net designer 
        /// via the SmartTag and then modify the css and reference it
        /// as an external stylesheet using this property.
        /// </summary>
        [DefaultValue("")]
        [Editor(typeof(System.Web.UI.Design.UrlEditor), typeof(UITypeEditor))]
        public virtual string ExternalStyleSheet
        {
            get
            {
                string url = (string)(ViewState["ExternalStyleSheet"] ?? string.Empty);
                if (url != string.Empty && !DesignMode)
                    return ResolveUrl(url);
                return url;
            }
            set
            {
                ViewState["ExternalStyleSheet"] = value;
            }
        }

        [Browsable(false)]
        public virtual bool IsCallback
        {
            get
            {
                if (DesignMode || Context == null) return false;
                string key = string.Format("iscallback${0}", UniqueID);
                string value = Context.Request.Form[key];
                return (!string.IsNullOrEmpty(value));
            }
        }

        /// <summary>
        /// This is not intended for use in user code
        /// </summary>
        [Browsable(false)]
        public virtual bool IsCustomSkin
        {
            get
            {
                return (!string.IsNullOrEmpty(CustomSkinFolder) &&
                            CustomSkin != "None");
            }
        }

        /// <summary>
        /// This is not intended for use in user code
        /// </summary>
        [Browsable(false)]
        public virtual bool IsDesignMode
        {
            get
            {
                return DesignMode;
            }
        }

        /// <summary>
        /// This is not intended for use in user code
        /// </summary>
        [Browsable(false)]
        public virtual string[] ManifestResourceNamesCache
        {
            get
            {
                return _manifestResourceNamesCache;
            }
        }

        /// <summary>
        /// when nesting the editor inside a templated control, it's very possible
        /// that the template is enabled based on a postback. Eg, if you nested
        /// the editor in the Edit template of a DetailsView, and click a toolBar
        /// button on the editor that does an async postback, you may need to 
        /// tell your serverside code to rebuild itself in edit mode. In order to do
        /// this, we also need to postback additional information, more specifically
        /// values contained in the form element that have nothing to do with the editor. 
        /// </summary>
        [DefaultValue(false)]
        public virtual bool PostbackAllFormData
        {
            get
            {
                return (ViewState["PostbackAllFormData"] == null) ?
                    false : (bool)ViewState["PostbackAllFormData"];
            }
            set
            {
                ViewState["PostbackAllFormData"] = value;
            }
        }

        /// <summary>
        /// Not for use in user code
        /// </summary>
        [Browsable(false)]
        public virtual string SelectedSkin
        {
            get
            {
                if (!string.IsNullOrEmpty(CustomSkin) &&
                    !CustomSkin.Equals("None"))
                    return CustomSkin;
                return Skin.ToString();
            }
        }

        /// <summary>
        /// Gets/Sets a skin. Builtin Skins available to the 
        /// control are contained within the control as resources. 
        /// </summary>
        [DefaultValue(typeof(SkinNames), "Classic"),
        RefreshProperties(RefreshProperties.Repaint)]
        public virtual SkinNames Skin
        {
            get
            {
                object o = (object)ViewState["Skin"];
                if (o == null)
                    o = SkinNames.Classic;
                return (SkinNames)o;
            }
            set
            {
                ViewState["Skin"] = value;
                if (DesignMode)
                {
                    ResetCollectionsAndStyles();
                    OnSelectedSkinChanged(EventArgs.Empty);
                }
            }
        }

        /// <summary>
        /// When this value is true, it will make use of the default HttpBrowserCapabilities
        /// classs to detect if it's in uplevel mode or not. You would be setting this to
        /// true if you already defined some proper browsercaps section in your web.config,
        /// otherwise don't bother. By default the control will try to parse the useragent
        /// manually and retrieve browsername/version number. This is done via the UserAgent class.
        /// </summary>
        [DefaultValue(false),
        Description("When true, uses default HttpBrowserCapabilities")]
        public bool UseBrowserCaps
        {
            get
            {
                return (bool)(ViewState["UseBrowserCaps"] ?? false);
            }
            set
            {
                ViewState["UseBrowserCaps"] = value;
            }
        }

        /// <summary>
        /// Gets/Sets a value indicating whether the CSS code generated by the editor maintains 
        /// readable formatting. The default is false, which means all spaces are removed to maintain a small
        /// css file footprint
        /// </summary>
        [DefaultValue(false)]
        internal virtual bool FormatCss
        {
            get
            {
                return _formatCss;
            }
            set
            {
                _formatCss = value;
            }
        }

        internal ResourceManager RM
        {
            get;
            set;
        }

        protected virtual string CallbackArgument
        {
            get
            {
                if (string.IsNullOrEmpty(_callbackArgument)&&!DesignMode)
                {
                    string key = string.Format("formdata${0}", UniqueID);
                    string value = Context.Request.Form[key];
                    if (!string.IsNullOrEmpty(value))
                        return value;
                }
                return _callbackArgument;
            }
            set
            {
                _callbackArgument = value;
            }
        }

        protected virtual string ClassName
        {
            get
            {
                if (this.CssClass == string.Empty)
                    return this.ClientID;
                return this.CssClass;
            }
        }

        protected abstract string CssKey
        {
            get;
        }

        protected virtual IDesignerHost Host
        {
            get
            {
                IDesignerHost host = null;
                if (DesignMode)
                    host = (IDesignerHost)this.Site.GetService(typeof(IDesignerHost));
                return host;
            }
        }

        [DefaultValue(true),
        Browsable(false)]
        protected virtual bool IsBrowserUpLevel
        {
            get
            {
                if (ViewState["IsBrowserUpLevel"] == null)
                    return (DesignMode) ? true : Helper.IsUpLevel(UseBrowserCaps);
                return (bool)ViewState["IsBrowserUpLevel"];
            }
            set
            {
                ViewState["IsBrowserUpLevel"] = value;
            }
        }

        protected abstract string KeyFormat
        {
            get;
        }

        protected abstract bool RenderSharedStyles
        {
            get;
        }

        protected abstract bool RenderTreeViewStyles
        {
            get;
        }

        protected virtual string RootImageSkinFolder
        {
            get { return _rootImageSkinFolder; }
        }

        protected virtual string RootSkinFolder
        {
            get { return _rootSkinFolder; }
        }

        protected virtual string RootTemplateSkinFolder
        {
            get { return _rootTemplateSkinFolder; }
        }

        protected ScriptManager SM
        {
            get;
            set;
        }

        protected abstract string StartupCssStyleIdentifier
        {
            get;
        }

        protected abstract string StyleSheetName
        {
            get;
        }

        protected bool SupportsStyleSheet
        {
            get
            {
                if ((((this.Page.Header != null)
                    && (this.Page.Header.StyleSheet != null))
                    && (Context.Request.Browser != null))
                    && (((Context.Request.Browser["preferredRenderingType"] != "xhtml-mp")
                    && Context.Request.Browser.SupportsCss)
                    && !this.Page.IsCallback))
                {
                    return true;
                }
                else
                { return false; }
            }
        }

        protected virtual string TransformationFileName
        {
            get { return "Preconfig.transformations.xml"; }
        }

        #endregion Properties

        #region Methods

        //public override void DataBind()
        //{
        //    this.OnDataBinding(EventArgs.Empty);
        //    this.EnsureChildControls();
        //    this.DataBindChildren();
        //}
        public virtual T FromJson<T>(string input)
        {
            return Helper.FromJson<T>(input);
        }

        public virtual string GetCssStyle()
        {
            return GetCssStyle(false);
        }

        public virtual string GetCssStyle(bool useDesigntimeResourceUrl)
        {
            string css = string.Empty;
            if (string.IsNullOrEmpty(ExternalStyleSheet))
            {
                string url = string.Format(
                    RootSkinFolder, SelectedSkin, IsCustomSkin? Helper.ConvertToPhysicalPath(StyleSheetName):StyleSheetName);

                css = GetTemplateData(url);

                css = TransformCssImageUrls(css, useDesigntimeResourceUrl);
            }
            else if (DesignMode)
            {
                string path = Helper.MapPath(Host, ExternalStyleSheet, PathType.Physical);
                using (StreamReader sr = new StreamReader(path))
                    css = sr.ReadToEnd();
            }
            return css;
        }

        public virtual string GetSharedCssStyle()
        {
            string url=string.Format(
                RootSkinFolder, SelectedSkin, IsCustomSkin?"Css/shared.css":"Css.shared.css");
            if (IsCustomSkin)
                url = Helper.GetPhysicalPathByResourceNamespace(url, this);

            string css = GetTemplateData(url);

            return TransformCssImageUrls(css, true);
        }

        public virtual string GetTreeViewCssStyle()
        {
            string url = string.Format(
                RootSkinFolder, SelectedSkin, IsCustomSkin?"Css/treeview.css":"Css.treeview.css");
            if (IsCustomSkin)
                url = Helper.GetPhysicalPathByResourceNamespace(url, this);

            string css = GetTemplateData(url);

            return TransformCssImageUrls(css, true);
        }

        void ICompositeControlDesignerAccessor.RecreateChildControls()
        {
            this.RecreateChildControls();
        }

        IEnumerable<ScriptDescriptor> IScriptControl.GetScriptDescriptors()
        {
            return GetScriptDescriptors();
        }

        IEnumerable<ScriptReference> IScriptControl.GetScriptReferences()
        {
            return GetScriptReferences();
        }

        public virtual string ToJsonString<T>(T input)
        {
            return Helper.ToJsonString<T>(input);
        }

        protected void EnsureScriptManager()
        {
            if (SM == null)
                SM = ScriptManager.GetCurrent(Page);
        }

        protected virtual string GetLocalizedText(string key)
        {
            return Helper.GetGlobalResourceObject(RM, string.Format(KeyFormat, key));
        }

        protected abstract IEnumerable<ScriptDescriptor> GetScriptDescriptors();

        protected abstract IEnumerable<ScriptReference> GetScriptReferences();

        protected virtual string GetTemplateData(string templateName)
        {
            Type t = typeof(Editor);
            string template = Helper.GetTemplateData(templateName,
                true, t, _manifestResourceNamesCache, RM, ClassName, ClientID);

            return template;
        }

        protected virtual DataSet GetTemplateFromResource(string templateName)
        {
            DataSet ds = null;
            string resourceName = (string)(Helper.GetManifestResourceName(templateName,
                    _manifestResourceNamesCache) ?? string.Empty);

            if (IsCustomSkin)
            {
                if (DesignMode && Host != null)
                    resourceName = Helper.MapPath(Host, templateName, PathType.Physical);
                else
                    resourceName = Context.Server.MapPath(templateName);
                ds = new DataSet();
                ds.ReadXml(resourceName);
            }
            else if (!string.IsNullOrEmpty(resourceName))
            {
                ds = new DataSet();
                using (Stream s = GetType().Assembly.GetManifestResourceStream(
                       resourceName))
                    ds.ReadXml(s);
            }
            return ds;
        }

        protected virtual void InitializeCSSRegistration()
        {
            if (this.DesignMode) return;

            if (!SM.IsInAsyncPostBack && string.IsNullOrEmpty(ExternalStyleSheet))
            {
                if (RenderTreeViewStyles)
                    Helper.RegisterSharedExternalStyleSheetReferences(this.Page,
                        QueryStringData(CssFile.Shared), QueryStringData(CssFile.TreeView));
                if(RenderSharedStyles)
                    Helper.RegisterSharedExternalStyleSheetReferences(this.Page,
                    QueryStringData(CssFile.Shared));
            }
            string id = StartupCssStyleIdentifier;
            Control c = Page.Header.FindControl(id);
            if (c != null) return;
            if (!string.IsNullOrEmpty(ExternalStyleSheet))
            {
                if (ExternalStyleSheet.IndexOf(@"App_Themes/",
                            StringComparison.OrdinalIgnoreCase) == -1)
                {
                    string link = "<link type=\"text/css\" rel=\"Stylesheet\" href=\"{0}\" />";
                    Literal l = new Literal();
                    l.EnableViewState = false;
                    l.ID = id;
                    l.Text = string.Format(link, ResolveUrl(ExternalStyleSheet));
                    Page.Header.Controls.Add(l);
                }
            }
            else if (this.Visible)
            {
                RegisterEmbededCss();
            }
        }

        protected virtual void OnSelectedSkinChanged(EventArgs e)
        {
            if (SelectedSkinChanged != null)
                SelectedSkinChanged(this, e);
        }

        protected abstract string QueryStringData(CssFile cssFileToRegister);

        protected virtual void RecreateChildControls()
        {
            base.ChildControlsCreated = false;
            this.EnsureChildControls();
        }

        protected virtual void RegisterEmbededCss()
        {
            EnsureScriptManager();
            //Is this really necessary?
            //do we really have to deposit the css directly vs linking to the stylesheet
            if (SM != null && SM.IsInAsyncPostBack)
            {
                string css = GetCssStyle();

                if (!FormatCss)
                    css = Helper.TrimWhiteSpace(css);

                if (!Helper.IsClientScriptBlockRegistered(CssKey, SM))
                {
                    //js doesn't supported line breaks in string literals
                    string styleBlock = Helper.GetStyleRegistrationByScriptBlock(css);
                    ScriptManager.RegisterClientScriptBlock(this, typeof(Editor), CssKey, styleBlock, true);
                }
                string sharedCssKey = "Typps.SharedCss";
                if (!Helper.IsClientScriptBlockRegistered(sharedCssKey, SM))
                {
                    string styleBlock = Helper.GetStyleRegistrationByScriptBlock(GetSharedCssStyle());
                    ScriptManager.RegisterClientScriptBlock(this, typeof(ResourceDummy), sharedCssKey, styleBlock, true);
                }
                if (RenderTreeViewStyles)
                {
                    string sharedTreeViewCssKey = "Typps.TreeViewCss";
                    if (!Helper.IsClientScriptBlockRegistered(sharedTreeViewCssKey, SM))
                    {
                        string styleBlock = Helper.GetStyleRegistrationByScriptBlock(GetTreeViewCssStyle());
                        ScriptManager.RegisterClientScriptBlock(this, typeof(ResourceDummy), sharedTreeViewCssKey, styleBlock, true);
                    }
                }
            }
            else
            {
                RegisterExternalStyleSheetReferences();
            }
        }

        protected virtual void RegisterExternalStyleSheetReferences()
        {
            string id = StartupCssStyleIdentifier;
            string url = Helper.GetEmbeddedWebResourceUrl(typeof(Editor), "css", true);
            string link = "<link type=\"text/css\" rel=\"Stylesheet\" href=\"{0}\" />";
            string href = string.Format("~/{0}{1}", url, QueryStringData(CssFile.Auto));
            Literal l1 = new Literal();
            l1.EnableViewState = false;
            l1.ID = id;
            l1.Text = string.Format(link, Helper.HashQueryString(ResolveUrl(href)));
            Page.Header.Controls.Add(l1);
        }

        protected virtual void RenderStylesAndScripts()
        {
            if (SM == null)
                throw new HttpException("A ScriptManager control must exist on the current page.");

            if (!this.DesignMode && !IsBrowserUpLevel)
            {
                InitializeCSSRegistration(); return;
            }
            else if (DesignMode)
            {
                this.EnsureChildControls();
            }
            else
            {
                EnsureScriptManager();
                if (SM != null && SM.SupportsPartialRendering)
                {
                    SM.RegisterScriptControl(this);
                    SM.RegisterScriptDescriptors(this);
                }
                InitializeCSSRegistration();
            }
        }

        protected abstract void ResetCollectionsAndStyles();

        protected virtual void ResponseWrite(string message, string contentType)
        {
            Context.Response.ClearContent();
            Context.Response.ContentType = contentType;
            Context.Response.Write(message);
        }

        protected virtual string TransformCssImageUrls(string css, bool useDesigntimeResourceUrl)
        {
            string fileName = string.Format(
                RootSkinFolder, SelectedSkin, TransformationFileName);
            if (IsCustomSkin)
                fileName = Helper.ConvertToPhysicalPath(fileName);

            using (DataSet ds = GetTemplateFromResource(fileName))
            {
                foreach (DataRow dr in ds.Tables[0].Rows)
                {
                    string resourceName = Helper.GetStringProperty("resource", dr);
                    string url = resourceName;
                    if (IsCustomSkin)
                        url = Helper.ConvertToPhysicalPath(resourceName);
                    string consumableUrl = Helper.GetBackgroundImageUrl(this,
                        string.Format(RootSkinFolder, SelectedSkin, url), useDesigntimeResourceUrl);
                    css = css.Replace(resourceName, consumableUrl);
                }
            }
            return css;
        }

        #endregion Methods
    }
}